﻿using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Windows;
using MagicKey.Components.Database;
using MagicKey.Components.FileAccess;
using MagicKey.Interfaces;

namespace MagicKey.Components.AccessManager
{
    public class AccessManager : IAccessManager
    {
        #region Members

        private static readonly IDatabase Database = new MagicDatabase();
        private IFileAccess fileAccess;
        private FtpHelper ftpHelper;

        #endregion

        #region Constructor

        #endregion

        #region Methods

        /// <summary>
        ///     Authorize an access to file
        /// </summary>
        /// <param name="masterId">owner of a file</param>
        /// <param name="slaveId">user whom access will be atributed</param>
        /// <param name="uri">uri of a file</param>
        /// <returns>true if authorisation was successfull else false</returns>
        public bool AuthorizeAccess(int masterId, int slaveId, string uri)
        {
            int uriId = Database.GetFileId(uri);
            return Database.InsertAuthorization(masterId, slaveId, uriId);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sInputFilename"></param>
        /// <returns></returns>
        public string GetEncryptedFilename(string sInputFilename)
        {
            string extension = Path.GetExtension(sInputFilename);
            string sOutputFilename = string.Empty;
            if (extension != null)
            {
                extension = extension.Substring(1);
                sOutputFilename = sInputFilename.Replace("." + extension, "");
                sOutputFilename = sOutputFilename + "_encrypted." + extension;
            }

            return sOutputFilename;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sInputFilename"></param>
        /// <returns></returns>
        public string GetDecryptedFilename(string sInputFilename)
        {
            string extension = Path.GetExtension(sInputFilename);
            string sOutputFilename = string.Empty;
            if (extension != null)
            {
                extension = extension.Substring(1);
                sOutputFilename = sInputFilename.Replace("." + extension, "");
                sOutputFilename = sOutputFilename + "_decrypted." + extension;
            }

            return sOutputFilename;
        }


        /// <summary>
        ///     Encrypt file symetric
        ///     <param name="sInputFilename">a filename included filepath</param>
        ///     <param name="sKey">symetric key</param>
        ///     <returns></returns>
        public string EncryptFileSym(string sInputFilename, string sKey)
        {
            CryptoStream cryptostream = null;
            FileStream fsEncrypted = null;
            FileStream fsInput = null;

            string sOutputFilename = GetEncryptedFilename(sInputFilename);

            if (!sOutputFilename.Equals(string.Empty))
            {
                try
                {
                    fsInput = new FileStream(sInputFilename,
                        FileMode.Open,
                        System.IO.FileAccess.Read);

                    fsEncrypted = new FileStream(sOutputFilename,
                        FileMode.Create,
                        System.IO.FileAccess.Write);

                    var encoding = new UTF8Encoding();

                    byte[] keyByteArray = encoding.GetBytes(sKey);

                    // Instantiate a new RijndaelManaged object to perform string symmetric encryption
                    var des = new DESCryptoServiceProvider();

                    var keyBytes = new byte[des.Key.Count()];

                    for (int i = 0; i < keyBytes.Count(); i++)
                    {
                        if (i < keyByteArray.Count())
                            keyBytes[i] = keyByteArray[i];
                    }

                    var ivBytes = new byte[des.IV.Count()];
                    byte[] ivByteArray = encoding.GetBytes("@1B2c3D4e5F6g7H8");

                    for (int i = 0; i < ivBytes.Count(); i++)
                    {
                        if (i < ivByteArray.Count())
                            ivBytes[i] = ivByteArray[i];
                    }

                    // Set key
                    des.Key = keyBytes;
                    des.IV = ivBytes;


                    ICryptoTransform desencrypt = des.CreateEncryptor();
                    cryptostream = new CryptoStream(fsEncrypted,
                        desencrypt,
                        CryptoStreamMode.Write);

                    var bytearrayinput = new byte[fsInput.Length];
                    fsInput.Read(bytearrayinput, 0, bytearrayinput.Length);
                    cryptostream.Write(bytearrayinput, 0, bytearrayinput.Length);
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }

                finally
                {
                    if (fsInput != null && cryptostream != null)
                    {
                        cryptostream.Close();
                        cryptostream.Dispose();
                        fsInput.Close();
                        fsInput.Dispose();
                        fsEncrypted.Close();
                        fsEncrypted.Dispose();
                    }
                }
            }
            return sOutputFilename;
        }


        /// <summary>
        ///     Decrypt file symetric
        /// </summary>
        /// <param name="sInputFilename">a filename included filepath</param>
        /// <param name="sKey">symetric key</param>
        public string DecryptFileSym(string sInputFilename, string sKey)
        {
            StreamWriter fsDecrypted = null;

            string sOutputFilename = GetDecryptedFilename(sInputFilename);

            if (!sOutputFilename.Equals(string.Empty))
            {

                try
                {
                    var encoding = new UTF8Encoding();

                    byte[] keyByteArray = encoding.GetBytes(sKey);

                    // Instantiate a new RijndaelManaged object to perform string symmetric encryption
                    var des = new DESCryptoServiceProvider();

                    var keyBytes = new byte[des.Key.Count()];

                    for (int i = 0; i < keyBytes.Count(); i++)
                    {
                        if (i < keyByteArray.Count())
                            keyBytes[i] = keyByteArray[i];
                    }

                    var ivBytes = new byte[des.IV.Count()];
                    byte[] ivByteArray = encoding.GetBytes("@1B2c3D4e5F6g7H8");

                    for (int i = 0; i < ivBytes.Count(); i++)
                    {
                        if (i < ivByteArray.Count())
                            ivBytes[i] = ivByteArray[i];
                    }

                    // Set key
                    des.Key = keyBytes;
                    des.IV = ivBytes;

                    //Create a file stream to read the encrypted file back.
                    var fsread = new FileStream(sInputFilename,
                        FileMode.Open,
                        System.IO.FileAccess.Read);

                    //Create a DES decryptor from the DES instance.
                    ICryptoTransform desdecrypt = des.CreateDecryptor();

                    //Create crypto stream set to read and do a 
                    //DES decryption transform on incoming bytes.
                    var cryptostreamDecr = new CryptoStream(fsread,
                        desdecrypt,
                        CryptoStreamMode.Read);


                    //Print the contents of the decrypted file.
                    fsDecrypted = new StreamWriter(sOutputFilename, false, Encoding.UTF8);
                    var streamReader = new StreamReader(cryptostreamDecr);
                    string t = streamReader.ReadToEnd();
                    fsDecrypted.Write(t);
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }
                finally
                {
                    if (fsDecrypted != null)
                    {
                        fsDecrypted.Flush();
                        fsDecrypted.Close();
                        fsDecrypted.Dispose();
                    }
                }
            }
            return sOutputFilename;
        }

        /// <summary>
        ///     Encrypt a file asymetric
        /// </summary>
        /// <param name="fileOwner"></param>
        /// <param name="plainFilename"></param>
        public string EncryptFileAsym(string fileOwner, string plainFilename)
        {

            string encryptedFileName = GetEncryptedFilename(plainFilename);

            // Variables
            FileStream encryptedFile = null;
            IDatabase database = new MagicDatabase();

            try
            {
                // Select target CSP
                var cspParams = new CspParameters {ProviderType = 1};

                //cspParams.ProviderName; // CSP name
                var rsaProvider = new RSACryptoServiceProvider(cspParams);

                // Read public key from file
                var publicKeyText = database.LoadParameterUser("publicKey", fileOwner);

                // Import public key
                rsaProvider.FromXmlString(publicKeyText);
                
                // Encrypt plain text
                byte[]  plainBytes = File.ReadAllBytes(plainFilename);
                
                //
                byte[] encryptedBytes = rsaProvider.Encrypt(plainBytes, true);

                // Write encrypted text to file
                encryptedFile = File.Create(encryptedFileName);

                //
                encryptedFile.Write(encryptedBytes, 0, encryptedBytes.Length);
            }
            catch (Exception ex)
            {
                // Any errors? Show them
                Console.WriteLine("Exception encrypting file! More info:");
                Console.WriteLine(ex.Message);
            }
            finally
            {
                if (encryptedFile != null)
                {
                    encryptedFile.Close();
                }
            }

            return encryptedFileName;
        }

        /// <summary>
        /// </summary>
        /// <param name="fileOwner"></param>
        /// <param name="fileOwnerPassword"></param>
        /// <param name="encryptedFileName"></param>
        public string DecryptFileAsym(string fileOwner, string fileOwnerPassword, string encryptedFileName)
        {
            const string privateKeyFileName = "";

            // Variables
            CspParameters cspParams = null;
            RSACryptoServiceProvider rsaProvider = null;
            StreamReader privateKeyFile = null;
            FileStream encryptedFile = null;
            StreamWriter plainFile = null;
            string plainText = "";
            byte[] encryptedBytes = null;
            byte[] plainBytes = null;
            string sOutputFilename = GetDecryptedFilename(encryptedFileName);
            IDatabase database = new MagicDatabase();

            try
            {
                // Select target CSP
                cspParams = new CspParameters {ProviderType = 1};

                //cspParams.ProviderName; // CSP name
                rsaProvider = new RSACryptoServiceProvider(cspParams);
                
                // Read private/public key pair from file
                var privateKeyTextEncrypted = database.LoadParameterUser("privateKey", fileOwner);

                //Decript private key
                var privateKeyText = DecryptStringSym(privateKeyTextEncrypted, fileOwnerPassword);

                //destroy password
                fileOwnerPassword = null;
                
                // Import private/public key pair
                rsaProvider.FromXmlString(privateKeyText);

                privateKeyText = null;

                // Read encrypted text from file
                encryptedFile = File.OpenRead(encryptedFileName);
                encryptedBytes = new byte[encryptedFile.Length];
                encryptedFile.Read(encryptedBytes, 0, (int)encryptedFile.Length);

                // Decrypt text
                plainBytes = rsaProvider.Decrypt(encryptedBytes, true);
                
                // Write decrypted text to file
                plainFile = File.CreateText(sOutputFilename);
                plainText = Encoding.Unicode.GetString(plainBytes);
                plainFile.Write(plainText);
            }
            catch (Exception ex)
            {
                // Any errors? Show them
                Console.WriteLine("Exception decrypting file! More info:");
                Console.WriteLine(ex.Message);
            }
            finally
            {
                if (encryptedFile != null)
                {
                    encryptedFile.Close();
                }
                if (plainFile != null)
                {
                    plainFile.Close();
                }
            }

            return sOutputFilename;
        }


        /// <summary>
        ///     Generate a 64 bits sysmetric Key
        /// </summary>
        /// <returns>a generate sysmetric key</returns>
        public string GenerateSysmKey()
        {
            // Create an instance of Symetric Algorithm. Key and IV is generated automatically.
            var desCrypto = (DESCryptoServiceProvider)DES.Create();

            // Use the Automatically generated key for Encryption. 
            return Encoding.ASCII.GetString(desCrypto.Key);
        }


        /// <summary>
        ///     Generate asymetric keys pair
        /// </summary>
        /// <returns>array of public and private key. first key in a array is a public key</returns>
        public string[] GenerateAsymKeysPair()
        {
            // Variables
            RSACryptoServiceProvider rsaProvider;
            var keyPair = new string[2];
            CspParameters cspParams;
            string publicKey;
            string privateKey;

            try
            {
                // Create a new key pair on target CSP
                cspParams = new CspParameters
                {
                    ProviderType = 1,
                    Flags = CspProviderFlags.UseArchivableKey,
                    KeyNumber = (int)KeyNumber.Exchange
                };
                //cspParams.ProviderName; // CSP name
                rsaProvider = new RSACryptoServiceProvider(cspParams);

                //
                cspParams = null;

                // Export public key
                publicKey = rsaProvider.ToXmlString(false);

                //
                keyPair[0] = publicKey;

                //
                publicKey = null;

                // Export private/public key pair 
                privateKey = rsaProvider.ToXmlString(true);

                //
                keyPair[1] = privateKey;

                //
                privateKey = null;
            }

            finally
            {
                // Do some clean up if needed
                cspParams = null;
                rsaProvider = null;
                publicKey = null;
                privateKey = null;
            }

            return keyPair;
        }

        /// <summary>
        ///     Hash a password with MD5 Algorithm
        /// </summary>
        /// <param name="password">a password</param>
        /// <returns>a hashed password</returns>
        public string HashPassword(string password)
        {
            string hash;

            using (MD5 md5Hash = MD5.Create())
            {
                hash = GetHash(md5Hash, password);
            }

            return hash;
        }


        /// <summary>
        ///     Generate a salt
        /// </summary>
        /// <returns>a random salt</returns>
        public string GenerateSalt()
        {
            var random = new Random();
            return random.Next(9999).ToString("X");
        }

        /// <summary>
        ///     Hash a hashed password and salt with MD5 Algorithm
        /// </summary>
        /// <param name="hashedPassword"></param>
        /// <param name="salt"></param>
        /// <returns></returns>
        public string HashHashedPasswordAndSalt(string hashedPassword, string salt)
        {
            string hash;

            using (MD5 md5Hash = MD5.Create())
            {
                hash = GetHash(md5Hash, hashedPassword + salt);
            }

            return hash;
        }

        /// <summary>
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public bool VerifyUserPassword(string username, string password)
        {
            string storedHashedPassword = GetHashedPasswordFromDatabase(username);
            string hashedPassword = HashPassword(password);
            string salt = GetUserSaltFromDatabase(username);
            bool isVerified;

            using (MD5 md5Hash = MD5.Create())
            {
                isVerified = VerifyHash(md5Hash, hashedPassword + salt, storedHashedPassword);
            }

            return isVerified;
        }

        /// <summary>
        /// </summary>
        /// <param name="plainText"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public string EncryptStringSym(string plainText, string key)
        {
            var encoding = new ASCIIEncoding();

            byte[] keyByteArray = encoding.GetBytes(key);

            // Instantiate a new RijndaelManaged object to perform string symmetric encryption
            var rijndaelCipher = new RijndaelManaged();

            var keyBytes = new byte[rijndaelCipher.Key.Count()];

            for (int i = 0; i < keyBytes.Count(); i++)
            {
                if (i < keyByteArray.Count())
                    keyBytes[i] = keyByteArray[i];
            }

            var ivBytes = new byte[rijndaelCipher.IV.Count()];
            byte[] ivByteArray = encoding.GetBytes("@1B2c3D4e5F6g7H8");

            for (int i = 0; i < ivBytes.Count(); i++)
            {
                if (i < ivByteArray.Count())
                    ivBytes[i] = ivByteArray[i];
            }

            // Set key
            rijndaelCipher.Key = keyBytes;
            rijndaelCipher.IV = ivBytes;


            // Instantiate a new MemoryStream object to contain the encrypted bytes
            var memoryStream = new MemoryStream();

            // Instantiate a new encryptor from our RijndaelManaged object
            ICryptoTransform rijndaelEncryptor = rijndaelCipher.CreateEncryptor();

            // Instantiate a new CryptoStream object to process the data and write it to the 
            // memory stream
            var cryptoStream = new CryptoStream(memoryStream, rijndaelEncryptor, CryptoStreamMode.Write);

            // Convert the plainText string into a byte array
            byte[] plainBytes = Encoding.ASCII.GetBytes(plainText);

            // Encrypt the input plaintext string
            cryptoStream.Write(plainBytes, 0, plainBytes.Length);

            // Complete the encryption process
            cryptoStream.FlushFinalBlock();

            // Convert the encrypted data from a MemoryStream to a byte array
            byte[] cipherBytes = memoryStream.ToArray();

            // Close both the MemoryStream and the CryptoStream
            memoryStream.Close();
            cryptoStream.Close();

            // Convert the encrypted byte array to a base64 encoded string
            string cipherText = Convert.ToBase64String(cipherBytes, 0, cipherBytes.Length);

            // Return the encrypted data as a string
            return cipherText;
        }

        /// <summary>
        /// </summary>
        /// <param name="cipherText"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public string DecryptStringSym(string cipherText, string key)
        {
            var encoding = new ASCIIEncoding();

            byte[] keyByteArray = encoding.GetBytes(key);

            // Instantiate a new RijndaelManaged object to perform string symmetric encryption
            var rijndaelCipher = new RijndaelManaged();

            var keyBytes = new byte[rijndaelCipher.Key.Count()];

            for (int i = 0; i < keyBytes.Count(); i++)
            {
                if (i < keyByteArray.Count())
                    keyBytes[i] = keyByteArray[i];
            }

            var ivBytes = new byte[rijndaelCipher.IV.Count()];
            byte[] ivByteArray = encoding.GetBytes("@1B2c3D4e5F6g7H8");

            for (int i = 0; i < ivBytes.Count(); i++)
            {
                if (i < ivByteArray.Count())
                    ivBytes[i] = ivByteArray[i];
            }

            // Set key
            rijndaelCipher.Key = keyBytes;
            rijndaelCipher.IV = ivBytes;

            // Instantiate a new MemoryStream object to contain the encrypted bytes
            var memoryStream = new MemoryStream();

            // Instantiate a new encryptor from our RijndaelManaged object
            ICryptoTransform rijndaelDecryptor = rijndaelCipher.CreateDecryptor();

            // Instantiate a new CryptoStream object to process the data and write it to the 
            // memory stream
            var cryptoStream = new CryptoStream(memoryStream, rijndaelDecryptor, CryptoStreamMode.Write);

            // Will contain decrypted plaintext
            string plainText = String.Empty;

            try
            {
                // Convert the ciphertext string into a byte array
                byte[] cipherBytes = Convert.FromBase64String(cipherText);

                // Decrypt the input ciphertext string
                cryptoStream.Write(cipherBytes, 0, cipherBytes.Length);

                // Complete the decryption process
                cryptoStream.FlushFinalBlock();

                // Convert the decrypted data from a MemoryStream to a byte array
                byte[] plainBytes = memoryStream.ToArray();

                // Convert the encrypted byte array to a base64 encoded string
                plainText = Encoding.ASCII.GetString(plainBytes, 0, plainBytes.Length);
            }
            finally
            {
                // Close both the MemoryStream and the CryptoStream
                memoryStream.Close();
                cryptoStream.Close();
            }

            // Return the encrypted data as a string
            return plainText;
        }


        // Call this function to remove the key from memory after use for security
        [DllImport("KERNEL32.DLL", EntryPoint = "RtlZeroMemory")]
        public static extern bool ZeroMemory(IntPtr destination, int length);

        /// <summary>
        ///     Hash a string with a Hash Algorithm
        /// </summary>
        /// <param name="hashAlgorithm">Hash algorith to use</param>
        /// <param name="input">string to hash</param>
        /// <returns>A hashed string input with a hash algorihtm</returns>
        private static string GetHash(HashAlgorithm hashAlgorithm, string input)
        {
            // Convert the input string to a byte array and compute the hash.
            byte[] data = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(input));

            // Create a new Stringbuilder to collect the bytes
            // and create a string.
            var sBuilder = new StringBuilder();

            // Loop through each byte of the hashed data 
            // and format each one as a hexadecimal string.
            foreach (byte t in data)
            {
                sBuilder.Append(t.ToString("x2"));
            }

            // Return the hexadecimal string.
            return sBuilder.ToString();
        }


        /// <summary>
        ///     Verify a hash against a string.
        /// </summary>
        /// <param name="hashAlgorithm"></param>
        /// <param name="input"></param>
        /// <param name="hash"></param>
        /// <returns></returns>
        private static bool VerifyHash(HashAlgorithm hashAlgorithm, string input, string hash)
        {
            // Hash the input.
            string hashOfInput = GetHash(hashAlgorithm, input);

            // Create a StringComparer an compare the hashes.
            StringComparer comparer = StringComparer.OrdinalIgnoreCase;

            return 0 == comparer.Compare(hashOfInput, hash);
        }

        /// <summary>
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        private static string GetUserSaltFromDatabase(string username)
        {
            return Database.LoadParameterUser("salt", username);
        }

        /// <summary>
        ///     Get hashed password from database
        /// </summary>
        /// <param name="username">a username of user who has hashed password</param>
        /// <returns>a hashed password</returns>
        private static string GetHashedPasswordFromDatabase(string username)
        {
            return Database.LoadParameterUser("password", username);
        }

        #endregion
    }
}